package daybook.io.formatter;

import daybook.exception.MissingCaseError;
import daybook.model.Account;
import daybook.model.Book;
import daybook.model.Commodity;
import daybook.model.transaction.InventoryEntry;
import daybook.model.transaction.LedgerEntry;
import daybook.model.transaction.Transaction;
import daybook.model.transaction.TransactionSummaryStatistics;
import daybook.util.Pair;
import daybook.util.tree.Tree;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;

/**
 * Created by crc on 16-9-2.
 */
public class DefaultFormatterFactory implements FormatterFactory {
    private final Book book;
    private final Map<Format, Formatter> cache;

    public DefaultFormatterFactory(Book book) {
        this.book = Objects.requireNonNull(book);
        this.cache = new ConcurrentHashMap<>();
    }

    @Override
    public Formatter get(Format format) {
        return cache.computeIfAbsent(format, this::create);
    }

    private Formatter create(Format format) {
        switch (format) {
            case JSON: return new JsonFormatter();
            case STRING: return new StringFormatter(book);
            default: throw new MissingCaseError(format);
        }
    }

    private static class JsonFormatter implements Formatter {
        @Override
        public Format getFormat() {
            return Format.JSON;
        }

        @Override
        public void writeLedger(Map<Account, List<LedgerEntry>> ledger, Writer writer) {
            ToJson.writeLedger(ledger, writer);
        }

        @Override
        public void writeBalanceSheet(NavigableMap<LocalDate, Tree<Pair<Account, BigDecimal>>> balanceSheets, LocalDate startDate, Writer writer) {
            ToJson.writeBalanceSheet(balanceSheets, startDate, writer);
        }

        @Override
        public void writeIncomeStatement(NavigableMap<LocalDate, Tree<Pair<Account, BigDecimal>>> incomeStatements, LocalDate startDate, Writer writer) {
            ToJson.writeIncomeStatement(incomeStatements, startDate, writer);
        }

        @Override
        public void writeCashFlow(NavigableMap<LocalDate, Map<Boolean, Map<Account, BigDecimal[]>>> cashFlows, LocalDate startDate, Set<Account> cashAccounts, Writer writer) {
            ToJson.writeCashFlow(cashFlows, startDate, cashAccounts, writer);
        }

        @Override
        public void writeInventory(Map<Commodity, List<InventoryEntry>> inventory, Writer writer) {
            ToJson.writeInventory(inventory, writer);
        }

        @Override
        public void writeStatistics(TransactionSummaryStatistics statistics, Writer writer) {
            ToJson.writeStatistics(statistics, writer);
        }

        @Override
        public void writeBook(Book book, Writer writer) {
            ToJson.writeBook(book, writer);
        }

        @Override
        public void writeTransactions(Stream<Transaction> transactions, Writer writer) {
            ToJson.writeTransactions(transactions, writer);
        }

        @Override
        public void writeCollection(Collection<?> ss, Writer writer) {
            ToJson.writeCollection(ss, writer);
        }
    }

    private static class StringFormatter implements Formatter {
        private final Book book;
        private final ToString toString;

        private StringFormatter(Book book) {
            this.book = Objects.requireNonNull(book);
            this.toString = new ToString(MustacheTemplateEngine.getInstance());
        }

        @Override
        public Format getFormat() {
            return Format.STRING;
        }

        @Override
        public void writeLedger(Map<Account, List<LedgerEntry>> ledger, Writer writer) {
            toString.writeLedger(ledger, book.getChartOfAccounts(), writer);
        }

        @Override
        public void writeBalanceSheet(NavigableMap<LocalDate, Tree<Pair<Account, BigDecimal>>> balanceSheets, LocalDate startDate, Writer writer) {
            toString.writeBalanceSheet(balanceSheets, startDate, writer);
        }

        @Override
        public void writeIncomeStatement(NavigableMap<LocalDate, Tree<Pair<Account, BigDecimal>>> incomeStatements, LocalDate startDate, Writer writer) {
            toString.writeIncomeStatement(incomeStatements, startDate, writer);
        }

        @Override
        public void writeCashFlow(NavigableMap<LocalDate, Map<Boolean, Map<Account, BigDecimal[]>>> cashFlows, LocalDate startDate, Set<Account> cashAccounts, Writer writer) {
            toString.writeCashFlow(cashFlows, startDate, cashAccounts, book.getChartOfAccounts(), writer);
        }

        @Override
        public void writeInventory(Map<Commodity, List<InventoryEntry>> inventory, Writer writer) {
            toString.writeInventory(inventory, writer);
        }

        @Override
        public void writeStatistics(TransactionSummaryStatistics statistics, Writer writer) {
            toString.writeStatistics(statistics, writer);
        }

        @Override
        public void writeBook(Book book, Writer writer) {
            toString.writeBook(book, writer);
        }

        @Override
        public void writeTransactions(Stream<Transaction> transactions, Writer writer) {
            transactions.forEach(tx -> {
                try {
                    writer.write(toString.transaction(tx, book) + System.lineSeparator());
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
        }

        @Override
        public void writeCollection(Collection<?> ss, Writer writer) {
            ss.forEach(obj -> {
                try {
                    writer.write(obj.toString() + System.lineSeparator());
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            });
        }
    }
}
